package net.villonanny;

import java.io.OutputStreamWriter;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import net.villonanny.entity.Server;

import org.apache.commons.configuration.AbstractFileConfiguration;
import org.apache.commons.configuration.SubnodeConfiguration;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;
import org.apache.log4j.Logger;

/**
 * Starts the Nanny, loads the configuration, starts each server, starts the console, 
 */
public class VilloNanny {
	private final static Logger log = Logger.getLogger(VilloNanny.class);
	public static SimpleDateFormat formatter = new SimpleDateFormat();
	private Map<String, Server> allServers; // ServerID -> Server instance
	private Date startTime = null;
	private static VilloNanny singleton;

	private VilloNanny(String[] args) {
		formatter = new SimpleDateFormat(ConfigManager.getString("/dateFormat", "EEE dd MMMM yyyy HH:mm:ss Z"));
		String waitUntil = Util.startTimeString(args);
		if (waitUntil!=null) {
			try {
				startTime = formatter.parse(waitUntil);
				// EventLog.log("Waiting until " + startTime);
			} catch (ParseException e) {
				String message = "Invalid date: " + waitUntil + "; format should be like \"" + formatter.format(new Date()) + "\"";
				EventLog.log(message);
				log.error(message, e);
			}
		}
	}
	
	public static void main(String[] args) {
		String s = VilloNanny.class.getSimpleName() + " " + Version.VERSION + " starting...";
		EventLog.log(s);
		EventLog.log("");
		EventLog.log("evt.start01", VilloNanny.class);
		EventLog.log("evt.start02", VilloNanny.class);
		EventLog.log("evt.start03", VilloNanny.class);
		EventLog.log("evt.start04", VilloNanny.class);
		EventLog.log("");
		Util.setUtf8(args);
		log.debug(String.format("Current os.name = %s, os.arch = %s, os.version = %s", System.getProperty("os.name"), System.getProperty("os.arch"), System.getProperty("os.version")));
		log.debug("Current locale = " + Locale.getDefault());
		log.debug("Current character encoding = " + new OutputStreamWriter(System.out).getEncoding());
		// Check if we need to create an initial configuration, otherwise load it
		if (!ConfigManager.isConfigurationThere()) {
			EventLog.log("evt.newConfigurationStart", VilloNanny.class);
			if (ConfigManager.createNewConfiguration(args)) {
				EventLog.log("evt.newConfigurationEnd01", VilloNanny.class);
				EventLog.log("evt.newConfigurationEnd02", VilloNanny.class, ConfigManager.CONFIGDIR);
			}
			return;
		} else {
			ConfigManager.loadConfiguration();
		}
		singleton = new VilloNanny(args);
		singleton.execute();
	}
	
	public static VilloNanny getInstance() {
		return singleton;
	}
	
	public Map<String, Server> getAllServers() {
		return allServers;
	}
	
	private void execute() {
		int totServers = 0;
		try {
			// First we create all servers and login, so that errors and user prompts won't occur at the delayed start
			// Create all servers
			allServers = createServerList();
			if (allServers.size()==0) {
				EventLog.log("No servers defined. Exiting...");
				return;
			}
			// Login to all servers, just to check credentials and prompt for missing passwords
			for (Server server : allServers.values()) {
				try {
					if (server.isEnabled()) {
						server.login();
						totServers++;
					}
				} catch (ConversationException e) {
					EventLog.log("Can't login, disabling server \"" + server.getServerDesc() + "\"");
					server.setEnabledAndStartStop(false);
				}
			}
			if (totServers==0) {
				EventLog.log("msg.noServers", this.getClass());
				return;
			}
			//
			// Delayed start
			if (startTime!=null) {
				Util.sleep(startTime.getTime() - System.currentTimeMillis());
			}
			//
			// Start Console
			Console c = Console.getInstance();
//			c.start();
			
//			System.out.println("Starting server...");
			
			// Start all servers
			for (Server server : allServers.values()) {
				server.begin();
			}
//			addConfigurationListener();
//			try {
//				c.join(); // This thread will wait here until the console is terminated
//			} catch (InterruptedException e) {
//				log.debug("Interrupted");
//			}
			
//			System.out.println("Waiting for servers to finsih...");
			// Wait for all servers to terminate
//			for (Server server : allServers.values()) {
//				try {
//					server.join();
//				} catch (InterruptedException e) {
//					log.debug("Interrupted");
//				}
//			}
			
//			System.out.println("Servers finsihed...");
		} catch (Exception e) {
			Util.log("Program error", e);
			EventLog.log(e.getMessage());
			EventLog.log("Aborting...");
		}
//		EventLog.log("...done.");
	}
	
	/**
	 * Add a configuration listener that updates the server list when the configuration changes
	 */
	private void addConfigurationListener() {
		ConfigManager.addListener(
			new ConfigurationListener() {
				public void configurationChanged(ConfigurationEvent event) {
					try {
						if (event.getType() == AbstractFileConfiguration.EVENT_RELOAD && !event.isBeforeUpdate()) {
							// Update server list
							Map<String, Server> addedServers = updateServerList(allServers);
							// Start all new servers
							for (Server server : addedServers.values()) {
								server.begin();
							}
						}
					} catch (Exception e) {
						log.error("Failed to reload configuration", e);
					}
				}
			}
		);		
	}

	/**
	 * Initial creation of the server list
	 * @return
	 */
	private Map<String, Server> createServerList() {
		return updateServerList(new HashMap<String, Server>());
	}
	
	/**
	 * If servers are added or deleted, modify the server list
	 * @param allServers the previous servers
	 * @return the map of added servers than need to be started
	 */
	private Map<String, Server> updateServerList(Map<String, Server> allServers) {
		List<Server> deletableServers = new ArrayList<Server>(allServers.values());
		Map<String, Server> addedServers = new HashMap<String, Server>();
		
		List<SubnodeConfiguration> serverConfigs = ConfigManager.configurationsAt("/server");
		for (SubnodeConfiguration serverConfig : serverConfigs) {
			String serverId = Server.idFromConfig(serverConfig);
			Server existingServer = allServers.get(serverId);
			if (existingServer==null) {
				// New server added
				Server newServer = new Server(serverConfig);
				addedServers.put(serverId, newServer);
				allServers.put(serverId, newServer);
			} else {
				// Existing server
				if (addedServers.values().contains(existingServer)) {
					// Server just added
					EventLog.log("evt.duplicateServer", this.getClass(), serverId);
					continue;
				}
				deletableServers.remove(existingServer); // Remove this server from the deletable servers
				// When the configuration is reloaded, the SubnodeConfiguration that was previously contained in the Server
				// is not automatically updated: 
				// http://commons.apache.org/configuration/apidocs/org/apache/commons/configuration/SubnodeConfiguration.html
				// so we force an update
				existingServer.updateConfig(serverConfig);
				// Check if enabled/disabled has changed on an existing server
				boolean enabledInConfig = serverConfig.getBoolean("/@enabled");
				existingServer.setEnabledAndStartStop(enabledInConfig);
			}
		}
		// Now removedServers contains all servers that are not in the configuration anymore
		// and addedServers contains all new servers
		// Stop and delete all removed servers
		for (Server removedServer : deletableServers) {
			removedServer.terminate();
			allServers.remove(removedServer.getServerId());
		}
		return addedServers;
	}



}
